module pkt_hdr_proc_enh #
(
    //Width of AXI_Stream interfaces in bits
    parameter DATA_WIDTH = 128,
    //Propagate tkeep signal
    parameter KEEP_ENABLE = (DATA_WIDTH > 8),
    //Width of tkeep signal in bits
    parameter KEEP_WIDTH = (DATA_WIDTH / 8),
    //Width of tuser in bits
    parameter USER_WIDTH = 72,

    parameter MAC_WIDTH = 48,

    parameter VALUE_WIDTH_CUSTOM_IP = 64,
    parameter USER_WIDTH_CUSTOM_IP = 136,
    parameter VALUE_WIDTH_CUSTOM_NUM = 8,
    parameter USER_WIDTH_CUSTOM_NUM = 24
)
(
    input  wire        clk,
    input  wire        rst,
    
    input  wire                         metadata_en,//TODO
    input  wire [MAC_WIDTH-1:0]         metadata_src_mac,
    input  wire [MAC_WIDTH-1:0]         metadata_dst_mac,
    input  wire [127:0]                 metadata_src_ip,
    input  wire [127:0]                 metadata_dst_ip,
    input  wire [7:0]                   metadata_num,
    input  wire [7:0]                   metadata_path_id,
    /*
    *axis input
    */
    input  wire [DATA_WIDTH - 1 : 0]    s_axis_tdata,
    input  wire [KEEP_WIDTH - 1 : 0]    s_axis_tkeep,
    input  wire                         s_axis_tvalid,
    output wire                         s_axis_tready,
    input  wire                         s_axis_tlast,
    input  wire [USER_WIDTH - 1 : 0]    s_axis_tuser,
    /*
    *
    *axis output
    */
    output wire [DATA_WIDTH - 1 : 0]    m_axis_tdata,
    output wire [KEEP_WIDTH - 1 : 0]    m_axis_tkeep,
    output wire                         m_axis_tvalid,
    input  wire                         m_axis_tready,
    output wire                         m_axis_tlast,
    output wire [USER_WIDTH - 1 : 0]    m_axis_tuser   
);
//abstract offsets
/*
*offset_s_ip = ip_offset + 8
*offset_path_id = ip_offset + 72
*/
localparam VALUE_WIDTH_CUSTOM_MAC = MAC_WIDTH*2;
localparam USER_WIDTH_CUSTOM_MAC = 112;
localparam OFFSET_MAC = 15'd0;
localparam VALUE_WIDTH_CUSTOM_TYPE = 8;
localparam USER_WIDTH_CUSTOM_TYPE = 24;
localparam PORT_WIDTH = 8;
localparam TID_WIDTH = 8;

// bus width assertions
initial begin
    if (KEEP_WIDTH * 8 != DATA_WIDTH) begin
        $error("Error: AXI stream interface requires byte (8-bit) granularity (instance %m)");
        $finish;
    end  
end
//abstract values
wire [VALUE_WIDTH_CUSTOM_MAC-1:0]   value_mac;
assign                              value_mac = {metadata_src_mac,metadata_dst_mac};

wire [255:0]                value_ip;
assign                      value_ip = {metadata_dst_ip,metadata_src_ip};

wire [7:0]                  value_path_id;
assign                      value_path_id = metadata_path_id;

wire [7:0]                  value_num;
assign                      value_num = metadata_num;

wire [7:0]                  value_type;
assign                      value_type = 8'h2;


wire [1:0]                  type_user;
assign                      type_user = s_axis_tuser[USER_WIDTH-48 +: 2];

wire [14:0]                 offset_ip;
wire [14:0]                 offset_path_id;
wire [14:0]                 offset_num;
wire [14:0]                 offset_type;

reg  [14:0]                 offset_ip_reg;
reg  [14:0]                 offset_path_id_reg;
reg  [14:0]                 offset_num_reg;
reg  [14:0]                 offset_type_reg;

assign                      offset_ip = offset_ip_reg;
assign                      offset_num = s_axis_tuser[8+:8]+'d10;
assign                      offset_path_id = s_axis_tuser[8+:8]+'d11;
assign                      offset_type = s_axis_tuser[8+:8]+'d1;






reg [USER_WIDTH_CUSTOM_MAC-1:0] m_axis_tuser_custom_mac;
reg [271:0]                 m_axis_tuser_custom_ip;
reg [23:0]                 m_axis_tuser_custom_num;
reg [23:0]                 m_axis_tuser_custom_type;
reg [47:0]                 m_axis_tuser_custom_path_id;
wire metadata_en_r;

hdr_modifier #(
    .DATA_WIDTH(DATA_WIDTH),
    .KEEP_ENABLE(KEEP_ENABLE),
    .KEEP_WIDTH(KEEP_WIDTH),
    .VALUE_WIDTH(VALUE_WIDTH_CUSTOM_IP),
    .USER_WIDTH(USER_WIDTH_CUSTOM_IP)
) hdr_modifier_ip(
    //sys signals
    .clk(clk),
    .rst(rst),
    //axis_custom_in
    .s_axis_tdata(s_axis_tdata),
    .s_axis_tkeep(s_axis_tkeep),
    .s_axis_tvalid(s_axis_tvalid),
    .s_axis_tready(s_axis_tready),
    .s_axis_tlast(s_axis_tlast),
    .s_axis_tuser(m_axis_tuser_custom_ip),
    //axis_custom_out
    .m_axis_tdata(mid_axis_tdata),
    .m_axis_tkeep(mid_axis_tkeep),
    .m_axis_tvalid(mid_axis_tvalid),
    .m_axis_tready(mid_axis_tready),
    .m_axis_tlast(mid_axis_tlast)
   
);



hdr_modifier #(
    .DATA_WIDTH(DATA_WIDTH),
    .KEEP_ENABLE(KEEP_ENABLE),
    .KEEP_WIDTH(KEEP_WIDTH),
    .VALUE_WIDTH(VALUE_WIDTH_CUSTOM_NUM),
    .USER_WIDTH(USER_WIDTH_CUSTOM_NUM)
) hdr_modifier_path_id(
    //sys signals
    .clk(clk),
    .rst(rst),
    //axis_custom_in
    .s_axis_tdata(mid_axis_tdata1),
    .s_axis_tkeep(mid_axis_tkeep1),
    .s_axis_tvalid(mid_axis_tvalid1),
    .s_axis_tready(mid_axis_tready1),
    .s_axis_tlast(mid_axis_tlast1),
    .s_axis_tuser(m_axis_tuser_custom_num),
    //axis_custom_out
    .m_axis_tdata(mid_axis_tdata2),
    .m_axis_tkeep(mid_axis_tkeep2),
    .m_axis_tvalid(mid_axis_tvalid2),
    .m_axis_tready(mid_axis_tready2),
    .m_axis_tlast(mid_axis_tlast2)
);



always @(posedge clk) begin
    if (rst) begin
        m_axis_tuser_custom_mac <= 0;
        m_axis_tuser_custom_ip <= 0;
        m_axis_tuser_custom_path_id <= 0;
        m_axis_tuser_custom_num <= 0;
        m_axis_tuser_custom_type <= 0;
    end else if (metadata_en_r) begin
        m_axis_tuser_custom_mac <= {1'b1, OFFSET_MAC, value_mac};
        m_axis_tuser_custom_ip <= {1'b1, offset_ip, value_ip};
        m_axis_tuser_custom_path_id <= {1'b1, offset_path_id, value_path_id};
        m_axis_tuser_custom_num <= {1'b1, offset_num, value_num};
        m_axis_tuser_custom_type <= {1'b1, offset_type, value_type};
    end else begin
        m_axis_tuser_custom_mac <= m_axis_tuser_custom_mac;
        m_axis_tuser_custom_ip <= m_axis_tuser_custom_ip;
        m_axis_tuser_custom_path_id <= m_axis_tuser_custom_path_id;
        m_axis_tuser_custom_num <= m_axis_tuser_custom_num;
        m_axis_tuser_custom_type <= m_axis_tuser_custom_type;
    end
end

always@* begin
    case(type_user)
        2'b00: begin //vlan(0),pppoe(0)
            offset_ip_reg <= 22; 
        end
        2'b01: begin //vlan(1),pppoe(1)
            offset_ip_reg <= 32; 
        end
        2'b10: begin //vlan(0),pppoe(1)
            offset_ip_reg <= 28; 
        end
        2'b11: begin //vlan(1),pppoe(0)
            offset_ip_reg <= 26; 
        end
        default: begin
            offset_ip_reg <= 0;
        end
    endcase
end
//connect wires
wire [DATA_WIDTH-1:0]                mid_axis_tdata0;
wire [KEEP_WIDTH-1:0]                mid_axis_tkeep0;
wire                                 mid_axis_tvalid0;
wire                                 mid_axis_tready0;
wire                                 mid_axis_tlast0;

wire [DATA_WIDTH-1:0]                mid_axis_tdata;
wire [KEEP_WIDTH-1:0]                mid_axis_tkeep;
wire                                 mid_axis_tvalid;
wire                                 mid_axis_tready;
wire                                 mid_axis_tlast;

wire [DATA_WIDTH-1:0]                mid_axis_tdata1;
wire [KEEP_WIDTH-1:0]                mid_axis_tkeep1;
wire                                 mid_axis_tvalid1;
wire                                 mid_axis_tready1;
wire                                 mid_axis_tlast1;

wire [DATA_WIDTH-1:0]                mid_axis_tdata2;
wire [KEEP_WIDTH-1:0]                mid_axis_tkeep2;
wire                                 mid_axis_tvalid2;
wire                                 mid_axis_tready2;
wire                                 mid_axis_tlast2;


wire [USER_WIDTH-1:0] s_axis_tuser_r;

Shift_Register #(
    .NPIPE_DEPTH(3),
    .DATA_WIDTH(USER_WIDTH)
) tuser_sr (
    .clock(clk),
    .reset(rst),
    .input_data(s_axis_tuser),
    .output_data(s_axis_tuser_r)
);

Shift_Register #(
    .NPIPE_DEPTH(2),
    .DATA_WIDTH(1)
) valid_sr (
    .clock(clk),
    .reset(rst),
    .input_data(metadata_en),
    .output_data(metadata_en_r)
);

assign m_axis_tuser = {8'h00, s_axis_tuser_r[2*PORT_WIDTH+:TID_WIDTH], {PORT_WIDTH{1'b0}}, s_axis_tuser_r[0+:PORT_WIDTH]};

endmodule